package com.dap.dao.interceptor;

import java.sql.Connection;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.reflect.FieldUtils;
import org.apache.ibatis.executor.statement.PreparedStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dap.dao.BasePo;
import com.dap.dao.constants.Constants;
import com.dap.dao.model.Order;
import com.dap.dao.mybatis.DaoSupportImpl;

/**
 * 分页拦截器
 * Edited by wangmin on 2019-03-01
 */
public abstract class AbstractInterceptor implements Interceptor {
    private final static Logger LOGGER = LoggerFactory.getLogger(AbstractInterceptor.class);

    private static final int CONNECTION_INDEX = 0;


    /**
     * 得到分页的SQL
     *
     * @param pageIndex 页码
     * @param pageSize 每页大小
     * @return 分页SQL
     */
    protected abstract String getPagingSql(String querySelect, int pageIndex, int pageSize);


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        RoutingStatementHandler statementHandler = (RoutingStatementHandler) invocation.getTarget();
        PreparedStatementHandler preparedStatHandler =
                (PreparedStatementHandler) FieldUtils.readField(statementHandler, "delegate", true);
        RowBounds rowBounds = (RowBounds) FieldUtils.readField(preparedStatHandler, "rowBounds", true);

        Connection connection = (Connection) invocation.getArgs()[CONNECTION_INDEX];
        BoundSql boundSql = preparedStatHandler.getBoundSql();
        String originalSql = compressSqlStr(boundSql.getSql());

        // 排序,组装sql
        Object parameter = preparedStatHandler.getParameterHandler().getParameterObject();
        if (parameter instanceof BasePo) {
            originalSql = generateOriginalSqlWithOrderBy(originalSql, (BasePo) parameter);
        }


        // 没有分页，直接返回原调用
        if (rowBounds == null || rowBounds == RowBounds.DEFAULT) {
            if (SelectForUpdateHelper.isSelectForUpdate()) {
                originalSql += SelectForUpdateHelper.getUpdateSql();
            }

            FieldUtils.writeField(boundSql, "sql", originalSql, true);
            SaveSqlLogUtil.saveSqlLog(boundSql);
            return invocation.proceed();
        }

        // 有分页, 分页sql不添加 for update语句
        else {
            // 1.获取总行数，将行数绑定到当前线程中
            if (TotalRecordHelper.isNeadTotalRowCount()) {
                String countSql = generateCountSql(originalSql);
                TotalRecordHelper.computeTotalRecord(countSql, preparedStatHandler, connection);
            }

            // 2.获取分页的结果集，重写分页sql
            String pagingSql = getPagingSql(originalSql, rowBounds.getOffset(), rowBounds.getLimit());

            FieldUtils.writeField(boundSql, "sql", pagingSql, true);
            FieldUtils.writeField(rowBounds, "offset", RowBounds.NO_ROW_OFFSET, true);
            FieldUtils.writeField(rowBounds, "limit", RowBounds.NO_ROW_LIMIT, true);
            SaveSqlLogUtil.saveSqlLog(boundSql);
            return invocation.proceed();
        }
    }

    /**
     * 根据BasePo中的 orderBy 属性 生成 ORDER BY 语句
     */
    private static String generateOriginalSqlWithOrderBy(String originalSql, BasePo parameter) {
        List<Order> orders = parameter.getOrderBy();
        if (orders != null && !orders.isEmpty()) {
            StringBuilder orderSql = new StringBuilder(originalSql).append(" ORDER BY ");
            Map<String, String> columnMapping = DaoSupportImpl.getPropertyColumnMapper()
                .get(parameter.getClass().getName() + Constants.BASE_RESULT_MAP);

            for (Order order : orders) {
                String columnName = order.getColumnName();
                if (columnMapping != null) {
                    String mappingColumn = columnMapping.get(columnName);
                    if (!StringUtils.isEmpty(mappingColumn)) {
                        columnName = mappingColumn;
                    }
                }
                orderSql.append(columnName).append(' ').append(order.getSort()).append(",");
            }

            originalSql = orderSql.deleteCharAt(orderSql.length() - 1).toString();
        }
        return originalSql;
    }


    /**
     * 将SQL语句压缩成一条语句，并且每个单词的间隔都是1个空格
     */
    protected static String compressSqlStr(String sql) {
        StringBuilder sb = new StringBuilder(sql.length());

        char lastChar = 0;
        for (int i=0; i<sql.length(); ++i) {
            char ch = sql.charAt(i);

            if (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t') {
                if (' ' != lastChar) {
                    sb.append(' ');
                    lastChar = ' ';
                }
            } else {
                sb.append(ch);
                lastChar = ch;
            }
        }

        return sb.toString();
    }


    /**
     * 获取查询总数对应的SQL
     * 
     * @param querySelect
     * @return
     */
    private static String generateCountSql(String querySelect) {
        final String upperQuerySelect = querySelect.toUpperCase();

        final int fromIndex = getFirstFromInsertPoint(upperQuerySelect);
        if (fromIndex < 0) {
            return "select 1";
        }

        final int orderIndex = getLastOrderByInsertPoint(upperQuerySelect);

        final String upperSelectPart = upperQuerySelect.substring(0, fromIndex);
        final boolean hasDistinct = SqlStringUtils.indexOfOutsideQuotationMarks(upperSelectPart, "SELECT DISTINCT") >= 0;

        final boolean hasGroupBy = SqlStringUtils.indexOfOutsideQuotationMarks(upperQuerySelect, " GROUP BY ") >= 0;

        // 如果SELECT 中包含 DISTINCT或者GROUP BY 只能在外层包含COUNT
        if (hasDistinct || hasGroupBy) {
            StringBuffer sb = new StringBuffer(orderIndex + 40);
            sb.append("SELECT count(1) AS count FROM (");
            if (hasDistinct || upperSelectPart.indexOf('?') >= 0) {
                sb.append(querySelect.substring(0, orderIndex));
            } else {
                // 这段优化有问题, 但是这哪是满足业务需求. 最正确的逻辑是保证select 后面的自定义变量没有被后面的语句用到
                sb.append("SELECT 1 ").append(querySelect.substring(fromIndex, orderIndex));
            }
            sb.append(" ) t");
            return sb.toString();
        } else {
            StringBuffer sb = new StringBuffer(orderIndex-fromIndex + 30);
            sb.append("SELECT count(1) AS count ");
            sb.append(querySelect.substring(fromIndex, orderIndex));
            return sb.toString();
        }
    }

    public static void main(String[] args) {
        System.out.println(generateCountSql("SELECT a from table"));
        System.out.println(generateCountSql("SELECT a from table GROUP BY b"));
        System.out.println(generateCountSql("SELECT DISTINCT a from table"));
        System.out.println(generateCountSql("SELECT DISTINCT a from table GROUP BY b"));

        System.out.println(generateCountSql("SELECT 'SELECT DISTINCT' from table"));
        System.out.println(generateCountSql("SELECT DISTINCT 'SELECT DISTINCT' from table"));
        System.out.println(generateCountSql("SELECT \"SELECT DISTINCT\" from table"));
        System.out.println(generateCountSql("SELECT 'SELECT DISTINCT\"\\\\',\"SELECT DISTINCT\\'\",'\\\\ GROUP BY \"',\"\\\\ GROUP BY '\" from table"));

        System.out.println(generateCountSql("select 'from' as `from`"));
        System.out.println(generateCountSql("select 'from' as ` from `"));
        System.out.println(generateCountSql("select 'from' as ` from ` from"));
        System.out.println(generateCountSql("select 'from' as ` from ` from a"));
    }


    /**
     * 得到SQL第一个正确的 FROM 的插入点
     */
    private static int getFirstFromInsertPoint(String upperQuerySelect) {
        final String searchStr = " FROM ";

        int searchStartIdx = 0;
        while (searchStartIdx < upperQuerySelect.length()) {
            int fromStartIndex = SqlStringUtils.indexOfOutsideQuotationMarks(upperQuerySelect, searchStr, searchStartIdx);
            if (fromStartIndex > 0) {
                if (isBracketMatch(upperQuerySelect, 0, fromStartIndex)) {
                    return fromStartIndex;
                } else {
                    searchStartIdx = fromStartIndex + searchStr.length();
                    continue;
                }
            } else {
                return -1;
            }
        }

        return -1;
    }

    /**
     * 得到最后一个 ORDER BY 的插入点位置
     */
    private static int getLastOrderByInsertPoint(String upperQuerySelect) {
        int orderIndex = SqlStringUtils.lastIndexOfOutsideQuotationMarks(upperQuerySelect, " ORDER BY ");
        if (orderIndex < 0) {
            orderIndex = upperQuerySelect.length();
        } else {
            if (!isBracketMatch(upperQuerySelect, orderIndex, upperQuerySelect.length())) {
                throw new RuntimeException("SQL 分页必须要有正确的 ORDER BY 语句!");
            }
        }
        return orderIndex;
    }


    /**
     * 判断括号"()"是否匹配
     * 
     * @param text 要判断的文本
     * @param beginIndex 检查的起始下标
     * @param endIndex 检查的结束下标
     * @return 如果匹配返回TRUE, 否则返回FALSE
     */
    private static boolean isBracketMatch(String text, int beginIndex, int endIndex) {
        int internal = 0;
        for (int i=beginIndex; i<endIndex; ++i) {
            char ch = text.charAt(i);
            if ('(' == ch) {
                ++ internal;
            } else if (')' == ch) {
                -- internal;
                if (internal < 0) {
                    return false;
                }
            }
        }

        return 0 == internal;
    }

}
